package shotgun.my.sweetutil;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.IntStream;

/**
 * 数据分页工具
 *
 * @author wulingming
 **/
public class DataPageUtil {
    private DataPageUtil() {
        throw new IllegalStateException("Utility class");
    }

    /**
     * 获取总页数
     *
     * @param totalCount 总记录数
     * @param pageSize   每页数量
     * @return 总页数
     */
    public static int getPageCount(int totalCount, int pageSize) {
        return (totalCount + pageSize - 1) / pageSize;
    }

    /**
     * 获取当前页的偏移量，通常用于数据库limit或集合起始下标
     *
     * 比如: limit(DataPageUtil.getCurrentPageOffset(page,pageSize),pageSize)
     *
     * @param pageNum  当前页号
     * @param pageSize 每页数量
     * @return 偏移量
     **/
    public static int getCurrentPageOffset(int pageNum, int pageSize) {
        return (pageNum - 1) * pageSize;
    }

    /**
     * 根据当前偏移量计算当前页数，通常用于：知道下标，来计算当前是第几页
     *
     * @param offset   偏移量(从0开始：集合下标、数据库limit偏移量)
     * @param pageSize 每页数量
     * @return 当前页
     * @author wulingming
     **/
    public static int getCurrentPageNum(int offset, int pageSize) {
        return (offset / pageSize) + 1;
    }


    /**
     * 获取分页流，从第一页开始。比如：总数20，每页5，返回结果：1,2,3,4
     *
     * @param totalCount 总记录数
     * @param pageSize   每页数量
     * @return 分页流，从第一页开始。
     **/
    public static IntStream getPageStream(int totalCount, int pageSize) {
        return getPageStream(totalCount, pageSize, 1);
    }

    /**
     * 获取分页流，从指定页开始，总页数不变。比如：总数20，每页5，起始页0，返回结果：0,1,2,3  原本是1-4页变为0-3页
     *
     * @param totalCount  总记录数
     * @param pageSize    每页数量
     * @param startAtPage 起始页 1,2,3....
     * @return 分页流
     **/
    public static IntStream getPageStream(int totalCount, int pageSize, int startAtPage) {
        return IntStream.range(startAtPage, startAtPage + getPageCount(totalCount, pageSize));
    }

    /**
     * 获取分页流，从下标0开始。可用于数据库和List分页。比如：总数20，每页5，返回结果：0,5,10,15
     *
     * @param totalCount 总记录数
     * @param pageSize   每页数量
     * @return 分页流(offset)
     **/
    public static IntStream getPageStreamForDb(int totalCount, int pageSize) {
        return getPageStream(totalCount, pageSize, 0).map(pageNum -> pageNum * pageSize);
    }

    /**
     * 循环获取数据并合并，不计算总记录数。while(true)检测pageSize和“测数据大小”，不一致则停止
     *
     * @param pageSize         每页大小
     * @param everyPageDataFun Integer为当前页号（pageNum）
     * @return @notnull 所有页数据集合
     **/
    public static <T> List<T> getPageNoCountCollect(int pageSize, Function<Integer, List<T>> everyPageDataFun) {
        return getPageNoCountCollect(pageSize, everyPageDataFun, 1);
    }

    /**
     * 循环获取数据并合并，不计算总记录数。while(true)检测pageSize和“测数据大小”，不一致则停止
     *
     * @param pageSize         每页大小
     * @param everyPageDataFun Integer为当前页号（pageNum）
     * @param startAtPage      起始页
     * @return @notnull 所有页数据集合
     **/
    public static <T> List<T> getPageNoCountCollect(int pageSize, Function<Integer, List<T>> everyPageDataFun, int startAtPage) {
        ArrayList<T> collectResult = new ArrayList<>(pageSize);
        getPageNoCount(pageSize, everyPageDataFun, collectResult, startAtPage);
        return collectResult;
    }

    /**
     * 循环获取数据，不计算总记录数。while(true)检测pageSize和“测数据大小”，不一致则停止
     *
     * @param pageSize         每页大小
     * @param everyPageDataFun Integer为当前页号（pageNum）
     **/
    public static <T> void getPageNoCount(int pageSize, Function<Integer, List<T>> everyPageDataFun) {
        getPageNoCount(pageSize, everyPageDataFun, 1);
    }

    /**
     * 循环获取数据，不计算总记录数。while(true)检测pageSize和“测数据大小”，不一致则停止
     *
     * @param pageSize         每页大小
     * @param everyPageDataFun Integer为当前页号（pageNum）
     * @param startAtPage      起始页
     **/
    public static <T> void getPageNoCount(int pageSize, Function<Integer, List<T>> everyPageDataFun, int startAtPage) {
        getPageNoCount(pageSize, everyPageDataFun, null, startAtPage);
    }

    /**
     * 循环获取数据并合并，不计算总记录数。while(true)检测pageSize和“测数据大小”，不一致则停止
     *
     * @param pageSize         每页大小
     * @param everyPageDataFun Integer为偏移量(offset)
     * @return @notnull 所有页数据集合
     **/
    public static <T> List<T> getPageNoCountCollect4Db(int pageSize, Function<Integer, List<T>> everyPageDataFun) {
        return getPageNoCountCollect4Db(pageSize, everyPageDataFun, 1);
    }

    /**
     * 循环获取数据并合并，不计算总记录数。while(true)检测pageSize和“测数据大小”，不一致则停止
     *
     * @param pageSize         每页大小
     * @param everyPageDataFun Integer为偏移量
     * @param startAtPage      起始页
     * @return @notnull 所有页数据集合
     **/
    public static <T> List<T> getPageNoCountCollect4Db(int pageSize, Function<Integer, List<T>> everyPageDataFun, int startAtPage) {
        ArrayList<T> collectResult = new ArrayList<>(pageSize);
        getPageNoCount(pageSize, pageNum -> everyPageDataFun.apply(getCurrentPageOffset(pageNum, pageSize)), collectResult, startAtPage);
        return collectResult;
    }

    /**
     * 循环获取数据，不计算总记录数。while(true)检测pageSize和“测数据大小”，不一致则停止
     *
     * @param pageSize         每页大小
     * @param everyPageDataFun Integer为偏移量
     **/
    public static <T> void getPageNoCount4Db(int pageSize, Function<Integer, List<T>> everyPageDataFun) {
        getPageNoCount4Db(pageSize, everyPageDataFun, 1);
    }

    /**
     * 循环获取数据并合并，不计算总记录数。while(true)检测pageSize和“测数据大小”，不一致则停止
     *
     * @param pageSize         每页大小
     * @param everyPageDataFun Integer为偏移量
     * @param startAtPage      起始页
     **/
    public static <T> void getPageNoCount4Db(int pageSize, Function<Integer, List<T>> everyPageDataFun, int startAtPage) {
        getPageNoCount(pageSize, pageNum -> everyPageDataFun.apply(getCurrentPageOffset(pageNum, pageSize)), null, startAtPage);
    }

    /**
     * while查数据最大循环次数(避免死循环)
     */
    public static final int WHILE_MAX_TIMES = 10000;

    /**
     * while循环获取数据
     *
     * @param pageSize         每页大小
     * @param everyPageDataFun 每页查询数据
     * @param collectResult    数据收集集合
     * @param startAtPage      起始页
     **/
    private static <T> void getPageNoCount(int pageSize, Function<Integer, List<T>> everyPageDataFun, List<T> collectResult, int startAtPage) {
        if (pageSize <= 0 || everyPageDataFun == null) {
            return;
        }
        while (true) {
            List<T> everyPageData = everyPageDataFun.apply(startAtPage);

            if (collectResult != null && everyPageData != null && !everyPageData.isEmpty()) {
                collectResult.addAll(everyPageData);
            }
            if (everyPageData == null || everyPageData.size() != pageSize) {
                break;
            }
            ++startAtPage;
            if (startAtPage >= WHILE_MAX_TIMES) {
                throw new IllegalArgumentException("触发死循环检测，循环次数=" + WHILE_MAX_TIMES + "，当前页对象=" + everyPageData.get(0).getClass().getName());
            }
        }
    }


//    public static void main(String[] args) {
//        IntStream pageStream = getPageStreamForDb(20, 5);
//        pageStream.forEach(value -> System.out.println(value));
//    }

    public static void main(String[] args) {
        List<Integer> dbList = Arrays.asList(-10, 1, 2, 3, 4, 5, 6, 7, 8, 9);

        List<Integer> testSubList = dbList.subList(10, 10);
        int pageSize = 2;
        //test1
        List<Integer> resultList = getPageNoCountCollect4Db(pageSize, offset -> dbList.subList(offset, Math.min(offset + pageSize, dbList.size())));
        System.out.println(dbList);
        System.out.println(resultList);
        System.out.println("循环获取数据并合并，不计算总记录数。while(true)检测pageSize和“测数据大小”，不一致则停止。结果："+dbList.toString().equals(resultList.toString()));

        //test2
        getPageNoCount4Db(pageSize, offset -> {
            List<Integer> integers = dbList.subList(offset, Math.min(offset + pageSize, dbList.size()));
            System.out.println(integers);
            return integers;
        });

        //test3
        getPageNoCount(pageSize, pageNum -> {
            System.out.println("当前页:" + pageNum);
            int offset = getCurrentPageOffset(pageNum, pageSize);

            List<Integer> integers = dbList.subList(offset, Math.min(offset + pageSize, dbList.size()));
            System.out.println(integers);
            return integers;
        });


    }
}
